/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package common

import (
	"encoding/json"
	"errors"
	"fmt"
	"math/rand"
	"regexp"
	"time"
	"unicode"

	"github.com/golang/protobuf/proto"
	"github.com/spf13/cobra"
	"hundsun.com/hsl/hschain/common/crypto"
	"hundsun.com/hsl/hschain/common/keystore"
	"hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/common/util"
	"hundsun.com/hsl/hschain/protos"
	"hundsun.com/hsl/hschain/protos/common"
	"hundsun.com/hsl/hschain/protos/ledger"
	service "hundsun.com/hsl/hschain/rpc/impl"
	"hundsun.com/hsl/hschain/rpc/jsonclient"
)

//IsChineseChar 判断是否中文
func IsChineseChar(str string) bool {
	for _, r := range str {
		if unicode.Is(unicode.Scripts["Han"], r) || (regexp.MustCompile("[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]").MatchString(string(r))) {
			return true
		}
	}
	return false
}

//BuildTransferTx  创建交易
func BuildTransferTx(execTy string, fromAccount keystore.Account, chainID string, to string, amount, fee uint64,
	ttl uint32, contractID *common.ContractIdentifier, payload []byte) (*ledger.Transaction, error) {
	rand.Seed(time.Now().Unix())
	tx := &ledger.Transaction{
		Ty:         execTy,
		From:       string(fromAccount.GetAddress()),
		To:         to,
		Value:      amount,
		Fee:        fee,
		Nonce:      rand.Uint64(),
		ContractId: contractID,
		CreateTime: time.Now().UnixNano() / int64(time.Millisecond),
		ChainID:    chainID,
		TTL:        ttl,
		Payload:    payload,
	}

	tx.Sign(fromAccount.GetKeyPair())
	return tx, nil
}

// BuildTxWithCmd TODO为了兼容外部调用暂时这么实现
func BuildTxWithCmd(cmd *cobra.Command, execType string, contractID *common.ContractIdentifier, payload []byte, opts ...CmdOption) (*ledger.Transaction, error) {
	msgOpts := GetCmdOption(opts)
	if msgOpts != nil {
		err := checkValidators(cmd, msgOpts.Validators)
		if err != nil {
			return nil, err
		}
	}

	chainID, _ := cmd.Flags().GetString(FlagChainID)
	to, _ := cmd.Flags().GetString(FlagTo)
	amount, _ := cmd.Flags().GetUint64(FlagAmount)
	fee, _ := cmd.Flags().GetUint64(FlagFee)
	ttl, _ := cmd.Flags().GetUint32(FlagTTL)

	fromAccount, err := GetFromAccount(cmd)
	if err != nil {
		return nil, fmt.Errorf("load from account from keystore file failed, err: %s", err.Error())
	}

	return BuildTransferTx(execType, fromAccount, chainID, to, amount, fee, ttl, contractID, payload)
}

func checkValidators(cmd *cobra.Command, validators map[string]Validator) error {
	for f, v := range validators {
		if v != nil {
			flagVal := cmd.Flag(f)
			if flagVal != nil {
				if err := v.Validate(flagVal.Value); err != nil {
					return fmt.Errorf("validate flag '%s' failed, err: %s ", f, err.Error())
				}
			} else {
				return fmt.Errorf("flag %s not existed", f)
			}
		}
	}
	return nil
}

// GetFromAccount ...
func GetFromAccount(cmd *cobra.Command) (keystore.Account, error) {
	keyStoreFilePath, _ := cmd.Flags().GetString(FlagKeyStoreFilePath)
	keyStorePasswd, _ := cmd.Flags().GetString(FlagKeyStoreFilePasswd)

	InitCryptoOpt(cmd)

	keyStore, err := parseKeyStore(keyStoreFilePath, keyStorePasswd)
	if err != nil {
		fmt.Printf("Parse key store failed, err: %s", err.Error())
		return nil, err
	}

	var fromAccount keystore.Account
	if cmd.Flag(FlagFrom) != nil {
		fromAddr := cmd.Flag(FlagFrom).Value.String()
		if len(fromAddr) > 0 {
			err = protos.Address(fromAddr).Validate()
			if err != nil {
				fmt.Printf("validate from address %s failed", fromAddr)
				return nil, err
			}
			fromAccount = keyStore.GetAccount(protos.Address(fromAddr))
			if fromAccount != nil {
				return fromAccount, nil
			}
			return nil, types.ErrNotFound
		}
	}
	return keyStore.GetAllAccounts()[0], nil
}

// AddTransactionPublicFlag add transaction public flag
func AddTransactionPublicFlag(cmd *cobra.Command, opts ...CmdOption) {
	cmd.Flags().String(FlagFrom, "", "from address(default as the first address in keystore file)")
	cmd.Flags().StringP(FlagTo, "t", "", "to address")
	cmd.Flags().Uint64P(FlagAmount, "a", 0, "amount")
	cmd.Flags().Uint64(FlagFee, 0, "transaction fee")
	cmd.Flags().Uint32(FlagTTL, 60, "transaction expire time")
	cmd.Flags().StringP(FlagExecTy, "e", "nativeToken", "executor type")
	cmd.Flags().BytesHex(FlagPayload, nil, "transaction payload")
	cmd.Flags().StringP(FlagKeyStoreFilePath, "f", "keystore.json", "key store file path")
	cmd.Flags().StringP(FlagKeyStoreFilePasswd, "s", "123456", "key store file password")
	cmd.Flags().String(FlagKeyAsymmetricAlgorithm, "ECDSA", "asymmetric algorithm")
	cmd.Flags().String(FlagKeyHashFamily, "SHA256", "hash family")
	cmd.Flags().String(FlagKeySymmetricAlgorithm, "AES", "symmetric algorithm")

	msgOpt := GetCmdOption(opts)
	if msgOpt != nil {
		for _, requiredFlag := range msgOpt.RequiredFlags {
			cmd.MarkFlagRequired(requiredFlag)
		}

		for _, hiddenFlag := range msgOpt.HiddenFlags {
			cmd.Flags().MarkHidden(hiddenFlag)
		}
	}
}

const (
	//CliConfigKeyKeystorePasswd keystore password config
	CliConfigKeyKeystorePasswd     = "keystore.passwd"
	configKeyNotFoundOrEmptyErrFmt = "config key '%s' in cli_config.yaml not found  or value is empty"
)

//parseKeyStore parse keystore
func parseKeyStore(keystoreFile, keystorePasswd string) (keystore.KeyStore, error) {
	if len(keystorePasswd) == 0 {
		return nil, fmt.Errorf(configKeyNotFoundOrEmptyErrFmt, CliConfigKeyKeystorePasswd)
	}
	keystore, err := keystore.LoadKeyStore(keystoreFile, keystorePasswd)
	if err != nil {
		return nil, fmt.Errorf("load key store failed, err: %s", err)
	}

	return keystore, nil
}

// InitCryptoOpt ...
func InitCryptoOpt(cmd *cobra.Command) {
	asymmetricAlgorithm := cmd.Flag(FlagKeyAsymmetricAlgorithm).Value.String()
	hashFamily := cmd.Flag(FlagKeyHashFamily).Value.String()
	symmetricAlgorithm := cmd.Flag(FlagKeySymmetricAlgorithm).Value.String()

	crypto.Init(func(c *crypto.Options) {
		c.AsymmetricAlgorithm = asymmetricAlgorithm
		c.HashFamily = hashFamily
		c.SymmetricAlgorithm = symmetricAlgorithm
	})
}

//RPCCall rpc call common function
func RPCCall(rpcLaddr string, methodName string, srcParam interface{}, resultFmt func(result interface{})) {
	var result service.JSONRPCResp
	param, err := AssembleGrpcRequest(methodName, srcParam)
	if err != nil {
		PrintError(err)
		return
	}

	ctx := jsonclient.NewRPCCtx(rpcLaddr, "JSONRPCServer.ProcessRPCRequest", param, &result)
	err = ctx.RunWithoutMarshal()
	if err != nil {
		PrintError(err)
		return
	}
	if ok := errorCallBack(result); !ok {
		return
	}
	if ok := nilCallBack(result); ok {
		return
	}

	if resultFmt != nil {
		resultFmt(result)
		return
	}
	commonCallBack(result)
}

//PrintJSON print result as json format
func PrintJSON(res interface{}) {
	str, err := json.MarshalIndent(res, "", "    ")
	if err == nil {
		res = string(str)
	}
	Print(res)
}

//Print ...
func Print(res interface{}) {
	fmt.Printf("call result:\n%v\n", res)
}

//PrintError print error result if exist
func PrintError(err error) {
	fmt.Printf("call error: %s\n", err.Error())
}

// 返回调用结果是否成功，不成功打印报错
func errorCallBack(obj service.JSONRPCResp) bool {
	if obj.Status != 0 {
		PrintError(errors.New(obj.Message))
		return false
	}
	return true
}

// 返回是否为空，打印空信息
func nilCallBack(obj service.JSONRPCResp) bool {
	if obj.Payload == nil {
		Print("empty")
		return true
	}
	return false
}

func commonCallBack(obj service.JSONRPCResp) {
	val, err := util.Bytes2Struct("", obj.Payload)
	if err != nil {
		PrintError(err)
		return
	}
	Print(val.(string))
}

// AssembleGrpcRequest assemble grpc request
func AssembleGrpcRequest(funcName string, param interface{}) (*service.JSONRPCArgs, error) {
	if param == nil {
		return &service.JSONRPCArgs{
			FuncName: funcName,
		}, nil
	}
	buf, err := util.Struct2Bytes(param)
	if err != nil {
		return nil, fmt.Errorf("serialize failed, reason:%v", err)
	}
	request := &service.JSONRPCArgs{
		FuncName: funcName,
		Param:    buf,
	}
	return request, nil
}

// CreateQueryContractReq create contract query request
func CreateQueryContractReq(driverName, funcName string, msg proto.Message) (*common.QueryContract, error) {
	param, err := proto.Marshal(msg)
	if err != nil {
		return nil, err
	}

	queryContract := &common.QueryContract{
		Driver:   driverName,
		FuncName: funcName,
		Param:    param,
	}
	return queryContract, nil
}
